package ch.ethz.fcl.metrobuzz.data.singapore;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.newdawn.slick.util.ResourceLoader;

import ch.ethz.fcl.metrobuzz.MBProperty;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.interchange.Interchange;
import ch.ethz.fcl.metrobuzz.data.interchange.Junction;
import ch.ethz.fcl.metrobuzz.data.interchange.JunctionReader;
import ch.ethz.fcl.metrobuzz.data.interchange.JunctionWriter;
import ch.ethz.fcl.metrobuzz.data.interchange.Region;
import ch.ethz.fcl.metrobuzz.data.interchange.RegionReader;
import ch.ethz.fcl.metrobuzz.data.interchange.RegionWriter;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.render.interchange.mrt.MRTInterchangeRender;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class SGInterchange {
	// private MBContainer container;
	private MBData data;
	private ArrayList<Junction> jns;
	private Map<Node, Junction> nodeJunctionMap;

	private ArrayList<Region>[] regions;

	private String folderName = MBProperty.INTERCHANGE_FOLDER;

	private Interchange selectedInterchange;
	private Interchange updatedSelectedInterchange;

	private JunctionReader junctionReader;
	private JunctionWriter junctionWriter;
	private RegionReader regionReader;
	private RegionWriter regionWriter;

	@SuppressWarnings("unchecked")
	public SGInterchange(MBData data) {
		this.data = data;

		if (folderName == null)
			return;
		File file = new File(folderName);
		if (!file.exists())
			folderName = "Singapore/interchange/";

		junctionReader = new JunctionReader(data);
		junctionWriter = new JunctionWriter(data);

		regionReader = new RegionReader(data);
		regionWriter = new RegionWriter(data);

		initJunctionNodes();

		if (MBProperty.REGIONS) {
			regions = (ArrayList<Region>[]) new ArrayList<?>[SGZone.LEVELS];
			for (int level = 0; level < SGZone.LEVELS; level++) {
				initRegions(level);
				MBTools.debug(regions[level].size() + " regions in level "
						+ level, true);
			}
		}
	}

	public void update() {
		int level = getLevel(data.getContainer().getViewer().getCamera()
				.getDistance());

		if (level < SGZone.LEVELS) {
			update(regions[level]);
		} else {
			update(jns);
		}
	}

	private void update(List<? extends Interchange> interchanges) {
		for (Interchange interchange : interchanges)
			interchange.initCircos();

		float max = 0;
		for (Interchange interchange : interchanges)
			if (interchange.getCircos() != null
					&& interchange.getCircos().getData().getTotalSum() > max)
				max = interchange.getCircos().getData().getTotalSum();

		for (Interchange interchange : interchanges)
			if (interchange.getCircos() != null)
				interchange.getCircos().setGlobalMax(max);
	}

	public void selectInterchange(float x, float y) {
		Point2D point = new Point2D(x, y);

		int level = getLevel(data.getContainer().getViewer().getCamera()
				.getDistance());

		selectedInterchange = null;
		if (level < SGZone.LEVELS)
			for (Region region : regions[level]) {
				if (region.getZone().getBound().contains(point)
						&& region.getZone().contains(point)) {
					selectedInterchange = region;
					updatedSelectedInterchange = region;
					break;
				}
				selectedInterchange = null;
			}
		else {
			float radius = data.getContainer().getViewer().getProjection()
					.getWorldDistance(30);
			float minDist = Float.MAX_VALUE;

			for (Junction jn : jns) {
				if (!MRTInterchangeRender.renderMRT(jn))
					continue;
				float dist = jn.getCircosCentroid().distance(point);
				if (dist < radius && dist < minDist) {
					selectedInterchange = jn;
					updatedSelectedInterchange = jn;
					minDist = dist;
				}
			}
		}
	}

	private void initRegions(int level) {
		String regionName = folderName + "Regions_" + level;
		File file = new File(regionName);
		if (!file.exists()) {
			regionWriter.setLevel(level);
			regions[level] = regionWriter.createRegions();
			regionWriter.writeRegions(this, regionName);
		}
		regions[level] = regionReader.readRegionInfo(regionName, level);

		for (Region region : regions[level])
			region.init(data, this, level);

		String regionDistributionName = folderName + "RegionDistribution_"
				+ level;

		if (!new File(regionDistributionName).exists()) {
			regionWriter.setLevel(level);
			regionWriter.setDistributionFile(regionDistributionName);
		}
		regionReader
				.readRegionDistribution(this, regionDistributionName, level);
	}

	private void initJunctionNodes() {
		String junctionFileName = folderName + "JunctionNodes";

		// map nodes to junctions
		nodeJunctionMap = new HashMap<Node, Junction>();

		if (!(new File(junctionFileName)).exists()
				|| ResourceLoader.getResource(junctionFileName) == null) {
			jns = junctionWriter.createJunctionNodes(this);
			junctionWriter.writeJunctionNodes(junctionFileName, this);
		}

		jns = junctionReader.readJunctions(this, junctionFileName);

		for (Junction j : jns) {
			for (Node n : j.getNodes())
				nodeJunctionMap.put(n, j);
		}

		// init junctions after read the information
		for (Junction j : jns)
			j.init(data, this, data.getRoadNetwork());

		String junctionDistributionFileName = folderName
				+ "JunctionDistribution";
		if (!(new File(junctionDistributionFileName)).exists()
				|| ResourceLoader.getResource(junctionDistributionFileName) == null) {
			junctionWriter.setDistributionFile(junctionDistributionFileName);
		}

		junctionReader.readJunctionDistribution(junctionDistributionFileName,
				this);
	}

	public JunctionWriter getJunctionWriter() {
		return junctionWriter;
	}

	public RegionWriter getRegionWriter() {
		return regionWriter;
	}

	public void mapNodeJunction(Node node, Junction junction) {
		nodeJunctionMap.put(node, junction);
	}

	public Junction getJunction(Node node) {
		return nodeJunctionMap.get(node);
	}

	public ArrayList<Junction> getJunctions() {
		return jns;
	}

	public Interchange getSelectedInterchange() {
		return selectedInterchange;
	}

	public Interchange getUpdatedSelectedInterchange() {
		return updatedSelectedInterchange;
	}

	public List<Region> getRegions(int level) {
		return regions[level];
	}

	public static int getLevel(float distance) {
		if (!MBProperty.REGIONS)
			return SGZone.LEVELS;

		if (distance > 1.0f)
			return 0;
		else if (distance > 0.85f)
			return 1;
		else if (distance > 0.7f)
			return 2;
		else if (distance > 0.55f)
			return 3;
		else if (distance > 0.4f)
			return 4;
		else if (distance > 0.25f)
			return 5;
		else if (distance > 0.1f)
			return 6;
		else
			return 7;
	}
}
